package com.yangyang.ralearn.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.yangyang.ralearn.client.Ralearn;
import com.yangyang.ralearn.net.frame.FrameDecoder;
import com.yangyang.ralearn.net.load.ILoad;
import com.yangyang.ralearn.net.message.AbstractMessage;

public abstract class BaseClientSimulatorHandler extends SimpleChannelHandler {

	private Channel channel;
	private ChannelFuture future;
	private ChannelFactory factory;
	private AbstractMessage asyncRecvMessage;

	private static BaseClientSimulatorHandler instance;

	public static BaseClientSimulatorHandler instance() {
		if (instance == null)
			instance = new ClassPathXmlApplicationContext(
					"applicationContext.xml")
					.getBean(BaseClientSimulatorHandler.class);

		return instance;
	}

	/**
	 * for debug
	 * 
	 * @param reader
	 * @return
	 */
	private int promote(BufferedReader reader) {
		Logger.getLogger(BaseClientSimulatorHandler.class).debug(
				"Select your choice :");
		Logger.getLogger(BaseClientSimulatorHandler.class).debug(
				"		1:create group!");
		Logger.getLogger(BaseClientSimulatorHandler.class)
				.debug("		2:observe!");
		String line = null;
		while (true) {
			try {
				line = reader.readLine();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (line != null && line.trim().length() > 0) {
				if (line.equalsIgnoreCase("exit"))
					return 0;
				try {
					return Integer.valueOf(line);
				} catch (Exception e) {
				}
			}
		}
	}

	public ChannelFuture start() {
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		Map<String, ILoad> assembles = context.getBeansOfType(ILoad.class);
		for (Entry<String, ILoad> entry : assembles.entrySet()) {
			Logger.getLogger(Ralearn.class).info("loading:" + entry.getKey());
			entry.getValue().onLoad(context);
		}

		String host = "localhost";
		int port = 8080;
		factory = new NioClientSocketChannelFactory(
				Executors.newCachedThreadPool(),
				Executors.newCachedThreadPool());
		ClientBootstrap bootstrap = new ClientBootstrap(factory);
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			public ChannelPipeline getPipeline() {
				return Channels.pipeline(new FrameDecoder(),
						BaseClientSimulatorHandler.this);
			}
		});

		bootstrap.setOption("tcpNoDelay", true);
		bootstrap.setOption("keepAlive", true);
		future = bootstrap.connect(new InetSocketAddress(host, port));
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return future;
	}

	public void stop() {
		future.awaitUninterruptibly();
		if (!future.isSuccess()) {
			future.getCause().printStackTrace();
		}
		future.getChannel().getCloseFuture().awaitUninterruptibly();
		factory.releaseExternalResources();
	}

	/**
	 * @param args
	 * 
	 *            public static void main(String[] args) { // TODO
	 *            Auto-generated method stub
	 * 
	 *            ApplicationContext context = new
	 *            ClassPathXmlApplicationContext( "applicationContext.xml");
	 * 
	 *            Map<String, ILoad> assembles =
	 *            context.getBeansOfType(ILoad.class); for (Entry<String, ILoad>
	 *            entry : assembles.entrySet()) {
	 *            Logger.getLogger(Ralearn.class).info("loading:" +
	 *            entry.getKey()); entry.getValue().onLoad(context); }
	 * 
	 *            String host = "localhost"; int port = 8080; final
	 *            BaseClientSimulatorHandler clientSimulatorHandler =
	 *            BaseClientSimulatorHandler .instance(); ChannelFactory factory
	 *            = new NioClientSocketChannelFactory(
	 *            Executors.newCachedThreadPool(),
	 *            Executors.newCachedThreadPool()); ClientBootstrap bootstrap =
	 *            new ClientBootstrap(factory); bootstrap.setPipelineFactory(new
	 *            ChannelPipelineFactory() { public ChannelPipeline
	 *            getPipeline() {
	 * 
	 *            return Channels.pipeline(new FrameDecoder(),
	 *            clientSimulatorHandler); } });
	 * 
	 *            bootstrap.setOption("tcpNoDelay", true);
	 *            bootstrap.setOption("keepAlive", true); ChannelFuture future =
	 *            bootstrap.connect(new InetSocketAddress(host, port));
	 * 
	 *            { BufferedReader reader = new BufferedReader(new
	 *            InputStreamReader( System.in));
	 * 
	 *            int choice; try { while ((choice =
	 *            clientSimulatorHandler.promote(reader)) != 0) { switch
	 *            (choice) { case 1: {
	 *            Logger.getLogger(BaseClientSimulatorHandler.class)
	 *            .debug("input domain file and problem file:"); String line =
	 *            reader.readLine(); ReqCreateGroupMessage message =
	 *            (ReqCreateGroupMessage) MessageFactory
	 *            .instance().createMessage( MessageId.REQ_CREATE_GROUP);
	 *            message.init(line.split(" ")[0], line.split(" ")[1]);
	 *            clientSimulatorHandler.sendMessage(message, (Integer[]) null);
	 *            } break; case 2: { ReqObserveMessage message =
	 *            (ReqObserveMessage) MessageFactory .instance()
	 *            .createMessage(MessageId.REQ_OBSERVE);
	 *            clientSimulatorHandler.sendMessage(message, (Integer[]) null);
	 *            } break; case 3: case 4: {
	 *            Logger.getLogger(BaseClientSimulatorHandler.class)
	 *            .debug("input action"); String line = reader.readLine();
	 *            AbstractMessage message = MessageFactory.instance()
	 *            .createMessage( choice == 3 ? MessageId.REQ_CANDO :
	 *            MessageId.REQ_DOACTION); if (choice == 3) ((ReqCanDoMessage)
	 *            message).init(line); else { // ((ReqDoActionMessage)
	 *            message).init(line); // byte result = //
	 *            clientSimulatorHandler.doAction(line); //
	 *            Logger.getLogger(ClientSimulatorHandler.class) //
	 *            .debug("recv message:" + result); } } break; }
	 * 
	 *            } } catch (IOException e) { // TODO Auto-generated catch block
	 *            e.printStackTrace(); } } }
	 */

	public void sendMessage(AbstractMessage message, Integer... writable) {
		if (this.channel != null) {
			ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
			FrameDecoder.encode(buffer, message, writable);
			channel.write(buffer);
			// buffer.discardReadBytes();
		}
	}

	private void lock() {
		synchronized (this) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public void unLock(AbstractMessage message) {
		synchronized (this) {
			this.asyncRecvMessage = message;
			this.notify();
		}
	}

	public AbstractMessage syncSendMessage(AbstractMessage message,
			Integer... writable) {
		this.sendMessage(message, writable);
		this.lock();
		return this.asyncRecvMessage;
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		// TODO Auto-generated method stub
		AbstractMessage message = (AbstractMessage) e.getMessage();
		if (message != null) {
			int id = ctx.getChannel().getId();
			try {
				message.Do(id, ctx.getChannel(), null, null);
				this.unLock(message);
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
				this.unLock(message);
			}
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
			throws Exception {
		// TODO Auto-generated method stub
		super.exceptionCaught(ctx, e);
	}

	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		// TODO Auto-generated method stub
		super.channelConnected(ctx, e);
		this.channel = ctx.getChannel();
	}

	@Override
	public void channelDisconnected(ChannelHandlerContext ctx,
			ChannelStateEvent e) throws Exception {
		// TODO Auto-generated method stub
		super.channelDisconnected(ctx, e);
		this.channel = null;
	}
}
